require 'semaphor'
require 'cgi'

# Encapsulates functionality for tracking controller action calls.
# 
module Semaphor
  module Tracking
    @@current_user_id_method = nil
    @@pages_not_to_track = []
    @@user_ids_not_to_track = []
    @@uris_not_to_track_while_not_logged_in = []
        
    def pages_not_to_track=(array)
      @@pages_not_to_track = array
    end
    module_function 'pages_not_to_track='.intern
    
    def user_ids_not_to_track=(array)
      @@user_ids_not_to_track = array
    end
    module_function 'user_ids_not_to_track='.intern
    
    def uris_not_to_track_while_not_logged_in=(array)
      @@uris_not_to_track_while_not_logged_in = array
    end
    module_function 'uris_not_to_track_while_not_logged_in='.intern
    
    def current_user_id_method=(method_name)
      @@current_user_id_method = method_name
    end
    module_function 'current_user_id_method='.intern
    
    def current_user_id_method
      @@current_user_id_method
    end
    module_function :current_user_id_method
  
    def self.append_features(mod)
      if current_user_id_method.nil?
        raise "You must set Semaphor::Tracking.current_user_id_method before including this module. \
This should be the name of the method that returns the current user id; or set to \
false to disable user tracking."
      end
      
      mod.append_after_filter :hit
      
      super
    end
  
    def current_website
      # Try to match the website just based on host. If we have multiple matches, then try
      # to refine it further based on host + port.
      website = Website.find(:all, :conditions => ["uri LIKE ?", "%#{request.host}%"])
      if website.size == 1
        website.first
      elsif website.size > 1
        Website.find(:first, :conditions => ["uri LIKE ?", "%#{request.host_with_port}%"])
      else
        nil
      end
    end
  
    def hit
      # only track other params for gets, since storing them for other methods might store sensitive info
      # (like passwords during login)
      if request.get?
        other_params = serialize_params(params)
      else
        other_params = nil
      end
      
      website = current_website
      
      referer = nil
      referer = request.referer unless website.nil? or request.referer =~ Regexp.new("^#{website.uri}")
      user_id = send(current_user_id_method)
    
      return if request.remote_ip == '127.0.0.1' and request.request_uri == '/' #exclude Monit hits
      @@user_ids_not_to_track.each {|id| return if id.to_i == user_id }
      @@pages_not_to_track.each {|page| return if page[:controller] == params[:controller] and page[:action] == params[:action] }
      @@uris_not_to_track_while_not_logged_in.each {|uri| return if request.request_uri == uri } unless user_id > 0
      
    
      Hit.create :website_id => website ? website.id : nil,
        :controller => params[:controller],
        :action => params[:action],
        :id_param => params[:id],
        :other_params => other_params,
        :method => request.method.to_s,
        :remote_ip => request.remote_ip,
        :user_agent => request.user_agent,
        :request_uri => request.request_uri,
        :referer => referer,
        :user_id => user_id
    end
    
    def serialize_params(params, hash = false)
      str = ""
      params.each do |k,v|
        next if ['controller', 'action', 'id', 'method'].include? k
      
        if k.kind_of? String
          k = CGI.escape(k)
          k = "#{hash}[#{k}]" if hash
        end
        
        if v.kind_of? Hash
          str += serialize_params(v, k)
        elsif v.kind_of? Array
          str += "&#{k}[]=" + v.collect{|v| v.kind_of?(String) ? CGI.escape(v) : v}.join("&#{k}[]=")
        else
          v = CGI.escape(v) if v.kind_of? String
          # prepend all keys with &, that way we can easily do regexp queries in SQL for particular keys or key-value pairs
          str += "&#{k}=#{v}" 
        end
      end
      return str
    end
  end
end